﻿using IWMAG.Scripting.AST.ObjectModel;
using IWMAG.Scripting.Helpers;
using IWMAG.Scripting.Compiler;
using IWMAG.Scripting.Types;
using System.Collections.Generic;
using IWMAG.Scripting.AST.Declarations;
using System;
using IWMAG.Scripting.AST.Statements;
using IWMAG.Scripting.Interpreter;
using IWMAG.Scripting.AST.Operators;

namespace IWMAG.Scripting.AST {
    public class FunctionDeclaration : CodeDeclaration {
        public Signature Signature;
        public CodeStatement Body;

        public FunctionDeclaration(Compiler.Lexeme l, Signature s, CodeStatement body)
            : base(l) {
            Signature = s;
            Body = body;
        }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            Signature.ReturnType = sym.Dealias(Signature.ReturnType);
            bool found = false;

            sym.OpenScope();

            sym.Lookahead[typeof(ReturnStatement)] = delegate {
                found = true;
                return Signature.ReturnType;
            };

            sym.Lookahead[typeof(WaitStatement)] = delegate {
                if (!Signature.IsAsync) {
                    SendError(error, "Function `" + Signature.Name + "` waits but isn't qualified with async.");
                }
                return Signature.ReturnType;
            };

            if (!Signature.IsAsync) {
                sym.Lookahead[typeof(IAsyncResult)] = delegate {
                    SendError(error, "Function `" + Signature.Name + "` calls an async function but isn't qualified with async.");
                    return Signature.ReturnType;
                };
            }

            foreach (VariableDeclaration decl in Signature.Parameters) {
                decl.Verify(sym, error);
                decl.Type = sym.Dealias(decl.Type);
            }

            sym.OpenScope();
            foreach (VariableDeclaration v in Signature.Parameters) {
                sym.Variables[v.Name] = v.Type;
            }

            Body.Verify(sym, error);
            sym.CloseScope();

            if (!sym.TypeExists(Signature.ReturnType)) {
                SendError(error, "Type " + Signature.ReturnType.ToString() + " does not dereference to a real type. Are you missing an import?");
            }

            if (!found && Signature.ReturnType != CodeType.Void) {
                SendError(error, "Function " + Signature.Name + " never returns a value of type " + Signature.ReturnType.ToString());
            }

            sym.CloseScope();
        }

        public IEnumerable<Continuation> ValueOf(ScriptEngine engine, RuntimeTable sym, Reference<RunValue> val, params RunValue[] args) {
            sym.OpenScope();
            for (int i = 0; i < Signature.Parameters.Length; i++) {
                sym.Variables[Signature.Parameters[i].Name] = args[i];
            }

            sym.Passback[typeof(ReturnStatement)] = (a) => {
                val.ReferencedValue = a as RunValue;
                return null;
            };

            foreach (Continuation wait in Body.Execute(engine, sym)) {
                if (wait.Command == Continuation.Token.Wait) {
                    yield return wait;
                } else if (wait.Command == Continuation.Token.Return) {
                    goto end;
                } else if (wait.Command == Continuation.Token.Killed) {
                    yield return wait;
                    yield break;
                }
            }
        end:
            sym.CloseScope();
        }
    }
}